﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using FBEntities.FBConnections;
using FBEntities.FBConnections.User;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using XDAFacebook;
using XFControls;
using XFControls.Utils;

namespace FBEntities.FBObjects
{
    public delegate void UpdateEventHandler(object sender, EventArgs e);

    public interface IFBObject
    {
        //event EventHandler FinishParse;

        void Parse(JObject text);
        //void ParseAsync(JObject text);
    }

    public interface IFBUpdatable : IFBObject
    {
        event UpdateEventHandler Updated;
    }

    public class FBObject : IFBObject
    {
        public long id;
        public string name;

        #region IFBObject Members

        public void Parse(JObject o)
        {
            foreach (FieldInfo field in GetType().GetFields().Where(field => o[field.Name] != null && o[field.Name].Type != JTokenType.Null))
            {
                if (field.FieldType.Equals(typeof(long)))
                {
                    JTokenType valType = o[field.Name].Type;
                    if (valType == JTokenType.String)
                    {
                        if (!string.IsNullOrEmpty((string)o[field.Name]))
                            field.SetValue(this, long.Parse((string)o[field.Name]));
                    }
                    else
                        field.SetValue(this, (long)o[field.Name]);
                }
                else if (field.FieldType.Equals(typeof(string)))
                    field.SetValue(this, (string)o[field.Name]);
            }
        }

        #endregion
    }

    public class FBGroup : IFBObject
    {
        public int version;
        public string name;
        public string id;
        public FBProxyPerson owner;
        public string description;
        public string link;
        public string privacy;
        public DateTime updated_time;
        public FBFeed feed;
        public FBMembers members;
        public FBPicture picture;

        [JsonIgnore]
        public FBFeed Feed
        {
            get { return feed ?? (feed = new FBFeed()); }
            protected set { feed = value; }
        }

        [JsonIgnore]
        public FBMembers Members
        {
            get { return members ?? (members = new FBMembers()); }
            protected set { members = value; }
        }

        [JsonIgnore]
        public FBPicture Picture
        {
            get { return picture ?? (picture = new FBPicture(id)); }
            protected set { picture = value; }
        }

        #region IFBObject Members

        public void Parse(JObject o)
        {
            if (o == null) return;
            FBParseUtilities.Parse(o, this);
        }

        public WebDownloaderWrapper GetMembers()
        {
            return new WebDownloaderWrapper("https://graph." + Settings.Domain + ".com/" + id + "/members?" + FBParseUtilities.authKey);
        }

        public WebDownloaderWrapper GetInfo()
        {
            string authKeyCode = FBParseUtilities.authKeySplit;
            var args = new Dictionary<string, string>();
            args.Add("access_token", authKeyCode);

            var ret = new WebDownloaderWrapper("https://graph." + Settings.Domain + ".com/" + id) { PostData = args };

            return ret;
        }

        public void Parse()
        {
            if (string.IsNullOrEmpty(id))
                return;

            string url = "https://graph." + Settings.Domain + ".com/" + id + "?" + FBParseUtilities.authKey;
            var wrapper = new WebDownloaderWrapper(url);
            string json = wrapper.Download();
            JObject o = JObject.Parse(json);
            Parse(o);
        }

        #endregion

        public FBPost ToPost()
        {
            var ret = new FBPost
            {
                id = id,
                from = new FBFrom { Picture = picture },
                name = name,
                description = description,
                //created_time = created_time,
                updated_time = updated_time,
                type = "groups"
            };

            return ret;
        }
    }


    public static class FBParseUtilities
    {
        private static string curDir;
        private static readonly string restApiUrl = "https://api." + Settings.Domain + ".com/method";
        private static Regex _dateReg = new Regex(@"(?<Year>[0-9]{4})-(?<Month>[0-9]{2})-(?<Day>[0-9]{2})T(?<Hour>[0-9]{2}):(?<Min>[0-9]{2}):(?<Sec>[0-9]{2})\+");

        public static string CurDir
        {
            get
            {
                if (string.IsNullOrEmpty(curDir))
                    curDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
                return curDir;
            }
        }

        public static string authKeySplit { get; private set; }

        public static string authKey
        {
            get { return Settings.auth_key; }
            set { Settings.auth_key = value; authKeySplit = value.Split('=').Last(); }
        }

        public static bool HasError(string resp)
        {
            return resp.Contains("OAuthException", StringComparison.CurrentCultureIgnoreCase);
        }

        public static WebDownloaderWrapper Like(string id)
        {
            string authKeyCode = authKeySplit;
            var args = new Dictionary<string, string>();
            args.Add("access_token", authKeyCode);

            var ret = new WebDownloaderWrapper(string.Format("https://graph." + Settings.Domain + ".com/{0}/likes", id)) { PostData = args };

            return ret;
        }

        public static WebDownloaderWrapper PostText(string id, string type, string text)
        {
            string authKeyCode = authKeySplit;
            var args = new Dictionary<string, string>();
            args.Add("access_token", authKeyCode);
            args.Add("message", text);

            return new WebDownloaderWrapper(string.Format("https://graph." + Settings.Domain + ".com/{0}/{1}", id, type)) { PostData = args };
        }

        internal static void AddOptionalParameter(IDictionary<string, string> dict, string key, long value)
        {
            if (value >= 0)
            {
                dict.Add(key, value.ToString());
            }
        }

        internal static void AddOptionalParameter(IDictionary<string, string> dict, string key, string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                dict.Add(key, value);
            }
        }

        public static string PostPictureGraph(string id, string filePath, string caption, Delegate Callback)
        {
            var parameterList = new Dictionary<string, string> { { "access_token", authKey.Split('=').Last() } };

            AddOptionalParameter(parameterList, "message", caption);

            string contType = "image/";
            switch (filePath.Split('.').Last().ToLower())
            {
                case "png":
                    contType += "png";
                    break;
                case "gif":
                    contType += "gif";
                    break;
                default:
                    contType += "jpeg";
                    break;
            }

            try
            {
                if (File.Exists(filePath))
                    return WebDownloader.GetFileQueryResponse(parameterList, new FileInfo(filePath), contType,
                                                              string.Format(
                                                                  "https://graph." + Settings.Domain + ".com/{0}/photos", id),
                                                              Callback);

                throw new Exception(Settings.LocalizationStrings.FileNotExist + filePath + "\"\n");
            }
            catch (WebException)
            {
                return "OAuthException";
            }
        }

        public static WebDownloaderWrapper MarkAsRead(string id)
        {
            string authKeyCode = authKeySplit;
            var args = new Dictionary<string, string>();
            args.Add("access_token", authKeyCode);

            return new WebDownloaderWrapper(string.Format("https://graph.{0}.com/{1}?unread=0", Settings.Domain, id)) { PostData = args };
        }

        public static WebDownloaderWrapper PostTextREST(string id, string method, string[] text)
        {
            string authKeyCode = authKeySplit;
            var args = new Dictionary<string, string> { { "access_token", authKeyCode }, { text[0], text[1] }, { "post_id", id } };

            return new WebDownloaderWrapper(string.Format("{0}/{1}", restApiUrl, method)) { PostData = args };
        }

        public static void Parse(JObject o, object item)
        {
            if (o == null)
                return;

            foreach (FieldInfo field in item.GetType().GetFields())
            {
                if (o[field.Name] == null)
                {
                    if (field.FieldType.Equals(typeof(int)))
                        field.SetValue(item, 0);
                    else if (field.FieldType.Equals(typeof(string)))
                        field.SetValue(item, "");
                    else if (field.FieldType.Equals(typeof(long)))
                        field.SetValue(item, 0);
                    else if (field.FieldType.Equals(typeof(DateTime)))
                        field.SetValue(item, new DateTime());
                    else if (field.FieldType.GetInterfaces().Count(i => i.Equals(typeof(IFBObject))) > 0)
                        field.SetValue(item, null);
                    continue;
                }

                if (testPrims(field, o, item))
                    continue;
                if (testObjs(field, o, item))
                    continue;
                if (testArrays(field, o, item))
                    continue;
            }
        }

        private static bool testArrays(FieldInfo field, JObject o, object item)
        {
            if (field.FieldType.GetGenericArguments().Count() == 0)
                return false;

            Type arg = field.FieldType.GetGenericArguments()[0];

            if (arg.GetInterfaces().Count(i => i.Equals(typeof(IFBObject))) > 0)
            {
                Type gen = field.FieldType.GetGenericArguments()[0];
                MethodInfo clear = field.FieldType.GetMethod("Clear");
                clear.Invoke(field.GetValue(item), null);

                MethodInfo method = field.FieldType.GetMethod("Add");

                var array = o[field.Name] as JArray;

                if (array != null)
                    foreach (JToken t in array)
                    {
                        if (t.Type == JTokenType.Null)
                            continue;

                        var obj = (IFBObject)Activator.CreateInstance(gen);
                        obj.Parse((JObject)t);

                        method.Invoke(field.GetValue(item), new object[] { obj });
                    }
            }
            else return false;

            return true;
        }

        private static bool testObjs(FieldInfo field, JObject o, object item)
        {
            if (field.FieldType.Equals(typeof(DateTime)) || field.FieldType.Equals(typeof(DateTime?)))
            {
                switch (o[field.Name].Type)
                {
                    case JTokenType.Integer:
                        {
                            var t = (long)o[field.Name];
                            var time = new DateTime(1970, 1, 1);
                            time = time.AddSeconds(t);

                            field.SetValue(item, time);
                        }
                        break;
                    case JTokenType.String:
                        {
                            //2010-05-30T06:01:35+0000
                            var date = (string)o[field.Name];
                            Match match = _dateReg.Match(date);
                            var time = new DateTime(int.Parse(match.Groups["Year"].Value),
                                                    int.Parse(match.Groups["Month"].Value),
                                                    int.Parse(match.Groups["Day"].Value),
                                                    int.Parse(match.Groups["Hour"].Value),
                                                    int.Parse(match.Groups["Min"].Value),
                                                    int.Parse(match.Groups["Sec"].Value));
                            field.SetValue(item, time);
                        }
                        break;
                }
            } //  If the field has the IFBObject interface
            else if (field.FieldType.GetInterfaces().Count(i => i.Equals(typeof(IFBObject))) > 0)
            {
                var obj = (IFBObject)Activator.CreateInstance(field.FieldType);
                var data = o[field.Name] as JObject;
                obj.Parse(data);
                field.SetValue(item, obj);
            }
            else if (field.FieldType.IsSubclassOf(typeof(FBConnection)))
            {
                var obj = (FBConnection)Activator.CreateInstance(field.FieldType);
                var data = o[field.Name] as JObject;
                obj.Parse(data);
                field.SetValue(item, obj);
            }
            else return false;

            return true;
        }

        private static bool testPrims(FieldInfo field, JObject o, object item)
        {
            if (field.FieldType.Equals(typeof(int)))
                field.SetValue(item, getJSONObject(o[field.Name], typeof(int)));
            else if (field.FieldType.Equals(typeof(string)))
                field.SetValue(item, getJSONObject(o[field.Name], typeof(string)));
            else if (field.FieldType.Equals(typeof(long)))
                field.SetValue(item, getJSONObject(o[field.Name], typeof(long)));
            else if (field.FieldType.Equals(typeof(double)))
                field.SetValue(item, getJSONObject(o[field.Name], typeof(double)));
            else return false;

            return true;
        }

        private static object getJSONObject(JToken jToken, Type type)
        {
            object ret = null;
            switch (jToken.Type)
            {
                case JTokenType.Float:
                    var f = (float)jToken;
                    ret = f;
                    break;
                case JTokenType.Integer:
                    var i = (long)jToken;
                    ret = i;
                    break;
                case JTokenType.String:
                    var s = (string)jToken;
                    ret = s;
                    break;
            }

            if (!type.IsInstanceOfType(ret))
            {
                if (ret != null)
                    return Convert.ChangeType(ret, type, CultureInfo.CurrentCulture);

                return type.IsValueType ? Activator.CreateInstance(type) : null;
            }
            return ret;
        }
    }
}